package com.moons.demo.aicamera.facerecognition;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.moons.demo.aicamera.facerecognition.config.DemoConfig;
import com.moons.demo.aicamera.facerecognition.util.FaceDataUtil;
import com.moons.demo.aicamera.facerecognition.util.RegisterHelper;
import com.moons.demo.aicamera.facerecognition.util.ThreadPool.ThreadPoolManager;
import com.xqd.sdk.libaicamera.AICamera;
import com.xqd.sdk.libaicamera.AICameraManager;
import com.xqd.sdk.libaicamera.face.FaceObject;
import com.xqd.sdk.libaicamera.face.FaceObjectParser;
import com.xqd.sdk.libaicamera.utils.Utils;

import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import androidx.appcompat.app.AppCompatActivity;

/**
 * 此demo为<a href="https://gitee.com/my10moons/face_recognition_demo">1:N人脸识别demo</a> <br/>
 * <p>注册的思路主要有2条</p>
 * 1.一个是从图像帧取一帧图片，然后送给摄像头识别，返回的人脸数据中，再根据人脸坐标去抠出人脸，保存到本地。<br/>
 * <p>
 * 2.另一种是从onFaceData接口中获取到人脸特征值，通过face ID去onFrameData接口中取图像帧，两者需要相同，不然抠出人脸会不准确，不完整。<br/>
 * <p>
 * * 本demo会使用两种方式演示，第一种缺点就是有延时，特别是摄像头前人数较多时，但编程，思路简单易懂。第二种相对复杂一点，我们是先获取人脸数据，再回去取图像数据，
 * 因为摄像头计算人脸属性、特征值是需要时间的，所以"当前"的画面的人脸是没有属性，特征值的，因而需要对"最后"的数十帧图像需要缓存，以便后面使用，如抠出人脸。<br/>
 *
 * @author 10moons
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";

    private SurfaceView mSurfaceView;
    private SurfaceView mFaceSurfaceView;
    private TextView mVersionTextView;
    private Button bt_reg_user_list;
    private Button bt_reg;
    private Button bt_setting;

    private ArrayBlockingQueue<byte[]> mFrameDataQueue;
    private ArrayBlockingQueue<byte[]> mFaceAttributeQueue;
    private ArrayBlockingQueue<FaceObject> mFaceObjectsQueue;

    private ArrayBlockingQueue<Bitmap> mBitmapFrameQueue;


    private DecodeThread mDecodeThread;
    private DrawFaceThread mDrawFaceThread;
    private DrawFrameThread mDrawFrameThread;

    private AICamera mAICamera;

    private long mLastFrameTime;


    private final Object mFrameCountLock = new Object();

    private boolean isPreviewAvailable;
    private boolean isOverlayAvailable;

    private int mNoFaceFrameCount = 0;

    private WeakReference<MainActivity> mMainActivityWeakReference;

    /**
     * 是否已注册
     */
    public boolean registed = true;

    private final Object mRegFromCameraLock = new Object();

    AICameraManager mAICameraManager;

    enum RegisteMethod {
        /**
         * 从onFaceData中的人脸数据中获取并注册
         */
        FromData,
        /**
         * 截取某一帧图像，使用摄像头识别人脸并注册
         */
        FromCamera
    }

    /**
     * 注册方式，默认使用FromData
     */
    private RegisteMethod mRegisteMethod = RegisteMethod.FromData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mMainActivityWeakReference = new WeakReference<>(this);


        ThreadPoolManager.getInstance().getBackgroundExecutorService().execute(new Runnable() {
            @Override
            public void run() {
                DemoConfig.getInstance().updateFolder();
                FaceDataUtil.getInstance().updateUsers();
            }
        });


        initViews();
        initDeviceListener();
        initQueues();
        initThread();
        mLastFrameTime = System.currentTimeMillis();
    }


    /**
     * 初始化设备监听
     */
    private void initDeviceListener() {

        mAICameraManager = AICameraManager.instance();

        mAICameraManager.setDeviceEventListener(new AICameraManager.DeviceEventListener() {
            @Override
            public void onDeviceAdded(AICamera aiCamera) {
                Log.d(TAG, "onDeviceAdded: " + aiCamera.getPath());
                if (aiCamera.open()) {

                    List<Size> previewSizes = aiCamera.getSupportPreviewSizes();
                    if (previewSizes.size() > 0) {
                        for (Size sz : previewSizes) {
                            Log.d(TAG, String.format("%d*%d", sz.getWidth(), sz.getHeight()));
                            if (is720P(sz)) {
                                Log.d(TAG, "onDeviceAdded: set 720p");
                                aiCamera.setPreviewSize(sz);
                            }
                        }
                    }

                    //设置回调
                    aiCamera.setCaptureCallback(mCaptureCallback);

                    aiCamera.startCapture();

                    mAICamera = aiCamera;


                }


            }

            @Override
            public void onDeviceRemoved(AICamera aiCamera) {
                aiCamera.setCaptureCallback(null);
                aiCamera.stopCapture();
                aiCamera.close();
                mAICamera = null;

            }
        });
        mAICameraManager.start();

    }

    private boolean is720P(Size size) {
        return ((size.getWidth() == 1280) && (size.getHeight() == 720) || ((size.getWidth() == 720) && (size.getHeight() == 1280)));
    }


    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: ");
    }

    @Override
    protected void onStop() {
        super.onStop();

        Log.d(TAG, "onStop: ");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
        if (mAICamera != null) {

            mAICamera.stopCapture();

            mAICamera.release();
        }
        if (mAICameraManager != null) {
            mAICameraManager.close();
        }
        if (mDecodeThread != null) {
            mDecodeThread.mRunning = false;
        }
        if (mDrawFaceThread != null) {
            mDrawFaceThread.mRunning = false;
        }
        if (mDrawFrameThread != null) {
            mDrawFrameThread.mRunning = false;
        }


    }


    private void initQueues() {
        mFrameDataQueue = new ArrayBlockingQueue<>(2, true);
        mFaceAttributeQueue = new ArrayBlockingQueue<>(2);
        mBitmapFrameQueue = new ArrayBlockingQueue<>(2);
        mFaceObjectsQueue = new ArrayBlockingQueue<>(2);

        mFrameDataQueue.clear();
        mFaceAttributeQueue.clear();
        mBitmapFrameQueue.clear();
        mFaceObjectsQueue.clear();
    }

    private void initThread() {
        mDecodeThread = new DecodeThread();
        mDrawFaceThread = new DrawFaceThread();
        mDrawFrameThread = new DrawFrameThread();
        mDecodeThread.start();
        mDrawFaceThread.start();
        mDrawFrameThread.start();
    }

    AICamera.CaptureCallback mCaptureCallback = new AICamera.CaptureCallback() {

        @Override
        public void onFrameData(byte[] data, AICamera camera) {

            if (!isPreviewAvailable) {
                return;
            }
//            Log.d(TAG, "onJpegFrame");
            long currentTime = System.currentTimeMillis();
            if ((currentTime - mLastFrameTime) < 20) {
//                Log.d(TAG, "ignore one frame");
            }
            synchronized (mFrameCountLock) {
                mNoFaceFrameCount++;
                if (mNoFaceFrameCount >= 10) {
                    clearOverlay(mFaceSurfaceView);
                    mNoFaceFrameCount = 0;
                }
            }
            if (!mFrameDataQueue.offer(data)) {
                mFrameDataQueue.clear();
                mFrameDataQueue.offer(data);
            }

            FrameCacheUtil.getInstance().addCacheFrame(data);
        }

        @Override
        public void onFaceData(byte[] data, AICamera camera) {
            if (!isPreviewAvailable) {
                return;
            }
//            Log.d(TAG, "onDataFrame");
            List<FaceObject> faceObjects = camera.getFaceObjectParser().getFaceObjectList(data, 0);

            //这里需要对数据进行筛选，有特征值的，和没特征值，但有属性的

            for (FaceObject faceObject : faceObjects) {

                if (faceObject.hasFeatures()) {

                    if (!mFaceObjectsQueue.offer(faceObject)) {
                        mFaceObjectsQueue.clear();
                        mFaceObjectsQueue.offer(faceObject);
                    }


                } else {

                    if (!mFaceAttributeQueue.offer(data)) {
//                Log.d(TAG, "failed to offer a data queue, clear");
                        mFaceAttributeQueue.clear();
                        mFaceAttributeQueue.offer(data);
                    }

                }


            }


        }
    };

    private void initViews() {
        mVersionTextView = findViewById(R.id.version);
        mVersionTextView.setText(AICamera.getSDKVersion());
        mSurfaceView = findViewById(R.id.surfaceView_camera_preview);
        mFaceSurfaceView = findViewById(R.id.surfaceView_face_view);
        bt_reg_user_list = findViewById(R.id.bt_reg_user_list);
        bt_reg = findViewById(R.id.bt_camera_reg);
        bt_setting = findViewById(R.id.bt_config);

        bt_reg_user_list.setOnClickListener(this);
        bt_reg.setOnClickListener(this);
        bt_setting.setOnClickListener(this);
        mSurfaceView.setZOrderOnTop(false);
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                holder.setFormat(PixelFormat.RGBA_8888);
                isPreviewAvailable = true;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isPreviewAvailable = false;
//                Log.d(TAG, "mSurfaceView    surfaceDestroyed: ");
            }
        });
        mFaceSurfaceView.setZOrderOnTop(true);
        mFaceSurfaceView.setZOrderMediaOverlay(true);
        mFaceSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                holder.setFormat(PixelFormat.TRANSLUCENT);
                isOverlayAvailable = true;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isOverlayAvailable = false;
//                Log.d(TAG, "mFaceSurfaceView    surfaceDestroyed: ");
            }
        });


    }

    private void DrawFrame(final Bitmap bitmap) {
        //        Log.d(TAG, "DrawFrame start");
        Rect dest = new Rect(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight());
        Paint paint = new Paint();
        //paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setFilterBitmap(true);
        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        Canvas canvas = mSurfaceView.getHolder().lockCanvas();
        if (canvas == null) {
            return;
        }
        canvas.drawBitmap(bitmap, src, dest, paint);
        mSurfaceView.getHolder().unlockCanvasAndPost(canvas);
        if (!bitmap.isRecycled()) {
            bitmap.recycle();
        }
//        Log.d(TAG, "DrawFrame end");
    }


    /**
     * 画人脸框
     *
     * @param faceSegmentList 人脸数据信息，可能有多个
     */
    private void DrawFaces(final List<FaceObject> faceSegmentList, Size size) {
//        Log.d(TAG, "DrawFaces");
        float widthScale = (float) mFaceSurfaceView.getWidth() / size.getWidth();
        float heightScale = (float) mFaceSurfaceView.getHeight() / size.getHeight();
        Canvas canvas = mFaceSurfaceView.getHolder().lockCanvas();
        if (canvas == null) {
            return;
        }
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        for (FaceObject segment : faceSegmentList) {
            Paint paint = new Paint();
            paint.setStrokeWidth(4);
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(Color.WHITE);
            paint.setAntiAlias(true);


            RectF rect = new RectF();
            rect.left = segment.rect().left;
            rect.right = segment.rect().right;
            rect.top = segment.rect().top;
            rect.bottom = segment.rect().bottom;

            float originFaceWidth = rect.width();
            float originFaceHeight = rect.height();


            float finalWidth = originFaceWidth * widthScale;
            float finalHeight = originFaceHeight * heightScale;
            float orginX = rect.left + originFaceWidth / 2;
            float orginY = rect.top + originFaceHeight / 2;
            float x = orginX * widthScale;
            float y = orginY * heightScale;

            rect.left = x - finalWidth / 2;
            rect.right = x + finalWidth / 2;
            rect.top = y - finalHeight / 2;
            rect.bottom = y + finalHeight / 2;

            canvas.drawRect(rect, paint);
        }
        mFaceSurfaceView.getHolder().unlockCanvasAndPost(canvas);
    }

    @Override
    public void onClick(View v) {

        switch (v.getId()) {

            case R.id.bt_camera_reg:

                registed = false;


                break;
            case R.id.bt_reg_user_list:

                Intent intent = new Intent(MainActivity.this, RegisterUserListActivity.class);
                startActivity(intent);

                break;

            case R.id.bt_config:


                break;

            default:

        }
    }

    private class DrawFrameThread extends Thread {

        private boolean mRunning;

        public DrawFrameThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    Bitmap bitmap = mBitmapFrameQueue.poll(5, TimeUnit.MILLISECONDS);
                    if (!isPreviewAvailable) {
                        if (bitmap != null) {
                            bitmap.recycle();
                            bitmap = null;
                            continue;
                        }
                    }
                    if (bitmap != null) {
                        DrawFrame(bitmap);
                    } else {
                        //Log.d(TAG, "poll data timeout");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private class DrawFaceThread extends Thread {

        private boolean mRunning;

        public DrawFaceThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    byte[] data = mFaceAttributeQueue.poll(5, TimeUnit.MILLISECONDS);
                    if (data != null && mAICamera != null) {

                        Size size = Utils.getSizeFromFaceData(data);

                        FaceObjectParser faceObjectParser = mAICamera.getFaceObjectParser();
                        if (faceObjectParser != null) {

                            List<FaceObject> faceObjectList = faceObjectParser.getFaceObjectList(data, 0);

                            if (!faceObjectList.isEmpty()) {

                                synchronized (mFrameCountLock) {
                                    mNoFaceFrameCount = 0;
                                    DrawFaces(faceObjectList, size);

                                }
                            }
                            final long faceFrameId = Utils.getIdFromFaceData(data);
                            if (mRegisteMethod == RegisteMethod.FromData && !registed) {
                                Log.d(TAG, "RegisteMethod.FromData: ");
                                registed = true;

                                final FaceObject faceObject = mFaceObjectsQueue.poll(10, TimeUnit.MILLISECONDS);
                                if (faceObject != null) {
                                    //简化
                                    ThreadPoolManager.getInstance().getBackgroundExecutorService().execute(new Runnable() {
                                        @Override
                                        public void run() {
                                            RegisterHelper.getInstance().registFace(mMainActivityWeakReference.get(), faceFrameId, faceObject);

                                        }
                                    });


                                }


                            }


                        }

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private class DecodeThread extends Thread {

        private boolean mRunning;

        public DecodeThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    byte[] frame = mFrameDataQueue.poll(5, TimeUnit.MILLISECONDS);
                    if (frame != null) {
//                        Log.d(TAG, "decode start");
                        Bitmap bitmap = BitmapFactory.decodeByteArray(frame, 0, frame.length);
                        if (bitmap != null) {

                            synchronized (mRegFromCameraLock) {

                                if (mRegisteMethod == RegisteMethod.FromCamera && !registed) {
                                    registed = true;

                                    //分辨率需要小于 1280*720
                                    Bitmap faceBitmap;
                                    int width = bitmap.getWidth();
                                    int height = bitmap.getHeight();

                                    while (width >= 1280 || height >= 720) {
                                        width = width / 2;
                                        height = height / 2;
                                    }

                                    faceBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
                                    RegisterHelper.getInstance().registFace(mMainActivityWeakReference.get(), mAICamera, faceBitmap);
                                }
                            }


//                            Log.d(TAG, "decode end");
                            if (!mBitmapFrameQueue.offer(bitmap)) {
//                                Log.d(TAG, "could not offer to mBitmapFrameQueue");
                            }
                        }


                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    public static void clearOverlay(SurfaceView outputView) {
        Canvas canvas = null;
        try {
            canvas = outputView.getHolder().lockCanvas();
            if (canvas == null) {
                return;
            }
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outputView.getHolder().unlockCanvasAndPost(canvas);
            } catch (Exception e) {
                Log.e("SurfaceDraw", "can not unlock canvas");
            }
        }
    }
}
